{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "view-in-github"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/nicolas-chaulet/torch-points3d/blob/master/notebooks/ObjectClassificationRSConv.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "FFL6wvW8jvjO"
   },
   "outputs": [],
   "source": [
    "# Setup packages can take some time (30 minutes or so)\n",
    "!pip install pyvista\n",
    "!pip install --upgrade jsonschema\n",
    "!pip install torch-points3d\n",
    "!apt-get install -qq xvfb libgl1-mesa-glx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "colab_type": "code",
    "id": "Yrg4vto4qewE",
    "outputId": "69a4aabe-0ffc-42c6-f575-57cded74d87c"
   },
   "outputs": [],
   "source": [
    "# Needed for remote rendering \n",
    "import os\n",
    "os.environ[\"DISPLAY\"] = \":99.0\"\n",
    "os.environ[\"PYVISTA_OFF_SCREEN\"]=\"true\"\n",
    "os.environ[\"PYVISTA_PLOT_THEME\"]=\"true\"\n",
    "os.environ[\"PYVISTA_USE_PANEL\"]=\"true\"\n",
    "os.environ[\"PYVISTA_AUTO_CLOSE\"]=\"false\"\n",
    "os.system(\"Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "8_eOTvwjjuK4"
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import sys\n",
    "from omegaconf import OmegaConf\n",
    "import pyvista as pv\n",
    "import torch\n",
    "import time\n",
    "import datetime"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "k14-WHdzDcvu"
   },
   "outputs": [],
   "source": [
    "DIR = \"\" # Replace with your root directory, the data will go in DIR/data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "0sS0YEUXjuLO"
   },
   "source": [
    "<p align=\"center\">\n",
    "  <img width=\"40%\" src=\"https://raw.githubusercontent.com/nicolas-chaulet/torch-points3d/master/docs/logo.png\" />\n",
    "</p>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "F1cLXfkkjuLP"
   },
   "source": [
    "# Classifying objects with Relation-Shape CNN\n",
    "This notebook explains how Torch Points3D can be used to solve an object classification task. We will use [ModelNet](https://modelnet.cs.princeton.edu/) as our dataset and [Relation-Shape CNN](https://github.com/Yochengliu/Relation-Shape-CNN) as our model architecture. The dataset contains CAD models of objects that belong to 40 different categories and the task is to retrieve the category of the object from its point cloud representation. There is also a smaller version well suited for quick testing that contains only 10 classes. The notebook covers the following aspects of Torch Points3D:\n",
    "\n",
    "1. Create a dataset with a data augmentation pipeline\n",
    "2. Instantiate a pre configured model\n",
    "3. Setup the data loaders\n",
    "4. Run a training\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cellView": "form",
    "colab": {},
    "colab_type": "code",
    "id": "7O3JPuIQExZJ"
   },
   "outputs": [],
   "source": [
    "#@title Choose ModelNet parameters {run: \"auto\"}\n",
    "MODELNET_VERSION=\"40\" #@param [\"10\", \"40\"]\n",
    "USE_NORMAL = True #@param {type:\"boolean\"}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "EwOtGLfijuLR"
   },
   "source": [
    "## The dataset\n",
    "We will use Torch Points3D to download and create the dataset (be patient, it takes some time...). It automatically downloads a pre sampled version of ModelNet (with 10,000 points per object) and the data will be stored under the `ROOT/data/modelnet` directory. Creating the raw dataset is as simple as:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 109
    },
    "colab_type": "code",
    "id": "P4iIhBJAjuLT",
    "outputId": "2b45c47c-98ad-4003-951a-6f9bb5c82269"
   },
   "outputs": [],
   "source": [
    "# The dataset will be downloaded the first time this cell is run\n",
    "from torch_points3d.datasets.classification.modelnet import SampledModelNet\n",
    "import torch_points3d.core.data_transform as T3D\n",
    "import torch_geometric.transforms as T\n",
    "\n",
    "dataroot = os.path.join(DIR, \"data/modelnet\")\n",
    "pre_transform = T.Compose([T.NormalizeScale(), T3D.GridSampling3D(0.02)])\n",
    "dataset = SampledModelNet(dataroot, name=MODELNET_VERSION, train=True, transform=None,\n",
    "                 pre_transform=pre_transform, pre_filter=None)\n",
    "dataset[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "F0cY35y1juLZ"
   },
   "source": [
    "Each sample is a CAD model of an object (bathtub, toilet, bed, etc...), the `pos` attribute contains the coordinates of points sampled on each face of the mesh while `norm` contains the normal vector. You will notice that we have applied two pre transforms to our dataset:\n",
    "\n",
    "- `NormalizeScale` normalises the scale of each object\n",
    "- `GridSampling3D` does a voxel downsizing of each point cloud with a resolution of 0.02 (in normalized scale). This ensures a homogeneous density.\n",
    "\n",
    "Let's visualise some of those examples before going any further (this might fail on colab, it is a bit random).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cellView": "form",
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 429
    },
    "colab_type": "code",
    "id": "OsFH72B9juLa",
    "outputId": "8397efd1-5ba2-451b-d8b3-17147205f160"
   },
   "outputs": [],
   "source": [
    "#@title Plot samples { run: \"auto\" }\n",
    "objectid_1 = 109 #@param {type:\"slider\", step:1, min:1, max: 3000}\n",
    "objectid_2 = 998 #@param {type:\"slider\", step:1, min:1, max: 3000}\n",
    "objectid_3 = 2002#@param {type:\"slider\", step:1, min:1, max: 3000}\n",
    "\n",
    "samples = [objectid_1,objectid_2,objectid_3]\n",
    "p = pv.Plotter(notebook=True,shape=(1, len(samples)),window_size=[1024,412])\n",
    "for i in range(len(samples)):\n",
    "    p.subplot(0, i)\n",
    "    sample = dataset[samples[i]].pos.numpy()\n",
    "    point_cloud = pv.PolyData(sample)\n",
    "    point_cloud['y'] = sample[:,1]\n",
    "    p.add_points(point_cloud)\n",
    "    p.camera_position = [-1,5, -10]\n",
    "p.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "FYE0OSUhjuLg"
   },
   "source": [
    "## Building a model\n",
    "As mentioned in the introduction we will use Relation-Shape CNN as our backbone architecture for the model classifier. Let's build our model by using Torch Points3D API (you can ignore the error, it comes from a third party library and all works just fine). We will use the normal vector as an input feature, therefore we will set the number of input channels (`input_nc`) to 3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "dWvt8XsojuLi"
   },
   "outputs": [],
   "source": [
    "from torch_points3d.applications.rsconv import RSConv\n",
    "# Ignore the error below, it comes from the tests of a third-party package that we use"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "ffGE6xUNjuLo"
   },
   "outputs": [],
   "source": [
    "class RSConvCLassifier(torch.nn.Module):\n",
    "    def __init__(self):\n",
    "        super().__init__() \n",
    "        self.encoder = RSConv(\"encoder\", input_nc= 3 * USE_NORMAL,output_nc = int(MODELNET_VERSION), num_layers=4)\n",
    "        self.log_softmax = torch.nn.LogSoftmax(dim=-1)\n",
    "    \n",
    "    @property\n",
    "    def conv_type(self):\n",
    "        \"\"\" This is needed by the dataset to infer which batch collate should be used\"\"\"\n",
    "        return self.encoder.conv_type\n",
    "    \n",
    "    def get_output(self):\n",
    "        \"\"\" This is needed by the tracker to get access to the ouputs of the network\"\"\"\n",
    "        return self.output\n",
    "    \n",
    "    def get_labels(self):\n",
    "        \"\"\" Needed by the tracker in order to access ground truth labels\"\"\"\n",
    "        return self.labels\n",
    "    \n",
    "    def get_current_losses(self):\n",
    "        \"\"\" Entry point for the tracker to grab the loss \"\"\"\n",
    "        return {\"loss_class\": float(self.loss_class)}\n",
    "    \n",
    "    def forward(self, data):\n",
    "        # Set labels for the tracker\n",
    "        self.labels = data.y.squeeze()\n",
    "\n",
    "        # Forward through the network\n",
    "        data_out = self.encoder(data)\n",
    "        self.output = self.log_softmax(data_out.x.squeeze())\n",
    "\n",
    "        # Set loss for the backward pass\n",
    "        self.loss_class = torch.nn.functional.nll_loss(self.output, self.labels)\n",
    "    \n",
    "    def backward(self):\n",
    "         self.loss_class.backward()    \n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "colab_type": "code",
    "id": "r-C7fltXjuLt",
    "outputId": "9601778b-5d25-47a0-f483-afa0a9c09737"
   },
   "outputs": [],
   "source": [
    "model = RSConvCLassifier()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "S30iNr4TjuLx"
   },
   "source": [
    "## Setting up the data loaders\n",
    "In this section, we will first build  a data loader using basic building blocks, and then we will see how one can use our dataset wrapper to assemble a dataset ready for training with its test and train data loader configured.\n",
    "\n",
    "Let's start with a vanillia data loader. There are two ways to assemble batches depending on the model that is used. In that case we will want dense batches where samples are concatenated along the first dimension, just like for batches of images. It is very important to ensure that each sample has got the same number of points, this can be achieved by applying a `FixedPoints` transform to the dataset. Here we will use 2048 points per object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "colab_type": "code",
    "id": "UMKyO1qPjuLy",
    "outputId": "f086069c-1c7a-4602-f82a-86ce858b8427"
   },
   "outputs": [],
   "source": [
    "from torch_points3d.datasets.batch import SimpleBatch\n",
    "NUM_WORKERS = 4\n",
    "BATCH_SIZE = 12\n",
    "\n",
    "transform = T.FixedPoints(2048)\n",
    "dataset = SampledModelNet(dataroot, name=MODELNET_VERSION, train=True, transform=transform,\n",
    "                 pre_transform=pre_transform, pre_filter=None)\n",
    "\n",
    "collate_function = lambda datalist: SimpleBatch.from_data_list(datalist)\n",
    "train_loader = torch.utils.data.DataLoader(\n",
    "    dataset, \n",
    "    batch_size=BATCH_SIZE, \n",
    "    shuffle=True, \n",
    "    num_workers=NUM_WORKERS,  \n",
    "    collate_fn=collate_function\n",
    ")\n",
    "next(iter(train_loader))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "FR1oib1ajuL4"
   },
   "source": [
    "If we were using a model that can handle packed data we would not need the `FixedPoints` transform at all and each object could have a different number of points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "colab_type": "code",
    "id": "t-L5aUdhjuL4",
    "outputId": "5af6c08e-58cd-45b0-8e41-7ba57758455f"
   },
   "outputs": [],
   "source": [
    "from torch_geometric.data import Batch\n",
    "\n",
    "dataset = SampledModelNet(dataroot, name=MODELNET_VERSION, train=True, transform=None,\n",
    "                 pre_transform=pre_transform, pre_filter=None)\n",
    "\n",
    "collate_function = lambda datalist: Batch.from_data_list(datalist)\n",
    "train_loader = torch.utils.data.DataLoader(\n",
    "    dataset, \n",
    "    batch_size=BATCH_SIZE, \n",
    "    shuffle=True, \n",
    "    num_workers=NUM_WORKERS,  \n",
    "    collate_fn=collate_function\n",
    ")\n",
    "next(iter(train_loader))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "6UkV3L28juL9"
   },
   "source": [
    "We also provide a higher level interface that builds the train and test data loaders for you based on which model you use. We use an OmegaConf config to instantiate it (or any object with a similar interface). The config must contain the arguments required by the dataset. We can also include the pre transforms, test transforms and train transforms. The transform pipeline will look like this:\n",
    "\n",
    "1. Sample a fixed number of points\n",
    "2. Add random noise to the positions\n",
    "3. Randomly rotate the point cloud around the z axis\n",
    "4. Add the normal vector to the features getting into the network\n",
    "\n",
    "The config will be as follow:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "kmcefLM9juL-"
   },
   "outputs": [],
   "source": [
    "yaml_config = \"\"\"\n",
    "task: classification\n",
    "class: modelnet.ModelNetDataset\n",
    "name: modelnet\n",
    "dataroot: %s\n",
    "number: %s\n",
    "pre_transforms:\n",
    "    - transform: NormalizeScale\n",
    "    - transform: GridSampling3D\n",
    "      lparams: [0.02]\n",
    "train_transforms:\n",
    "    - transform: FixedPoints\n",
    "      lparams: [2048]\n",
    "    - transform: RandomNoise\n",
    "    - transform: RandomRotate\n",
    "      params:\n",
    "        degrees: 180\n",
    "        axis: 2\n",
    "    - transform: AddFeatsByKeys\n",
    "      params:\n",
    "        feat_names: [norm]\n",
    "        list_add_to_x: [%r]\n",
    "        delete_feats: [True]\n",
    "test_transforms:\n",
    "    - transform: FixedPoints\n",
    "      lparams: [2048]\n",
    "    - transform: AddFeatsByKeys\n",
    "      params:\n",
    "        feat_names: [norm]\n",
    "        list_add_to_x: [%r]\n",
    "        delete_feats: [True]\n",
    "\"\"\" % (os.path.join(DIR, \"data\"),MODELNET_VERSION, USE_NORMAL, USE_NORMAL)\n",
    "\n",
    "from omegaconf import OmegaConf\n",
    "params = OmegaConf.create(yaml_config)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 495
    },
    "colab_type": "code",
    "id": "LkGJygG2juMD",
    "outputId": "6eb60cce-9ed8-4d92-db21-6d033b1405cf"
   },
   "outputs": [],
   "source": [
    "# Instantiate dataset\n",
    "from torch_points3d.datasets.classification.modelnet import ModelNetDataset\n",
    "dataset = ModelNetDataset(params)\n",
    "dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Z86lKgNejuMI"
   },
   "source": [
    "We can see here that the train and test transforms have been set properly. The batch size is still not set since the data loaders have not been instantiaded yet, let's do that now and we will have a dataset ready for training. We can also instantiate the tracker directly from the dataset which reduces potential risks in using the wrong metrics."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "EfVbU3VHjuMJ"
   },
   "outputs": [],
   "source": [
    "# Setup the data loaders\n",
    "dataset.create_dataloaders(\n",
    "    model, \n",
    "    batch_size=BATCH_SIZE, \n",
    "    shuffle=True, \n",
    "    num_workers=NUM_WORKERS, \n",
    "    precompute_multi_scale=False\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 35
    },
    "colab_type": "code",
    "id": "YC0wA65jjuMN",
    "outputId": "159200f0-a73e-4245-caee-4b95e8142348"
   },
   "outputs": [],
   "source": [
    "next(iter(dataset.test_dataloaders[0]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "oD9MKKlyjuMR"
   },
   "outputs": [],
   "source": [
    "# Setup the tracker and actiavte tensorboard loging\n",
    "logdir = \"\" # Replace with your own path\n",
    "logdir = os.path.join(logdir, str(datetime.datetime.now()))\n",
    "os.mkdir(logdir)\n",
    "os.chdir(logdir)\n",
    "tracker = dataset.get_tracker(False, True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Cwz7oGwLjuMV"
   },
   "source": [
    "## Training!\n",
    "We can now start our training loop, we will use the Adam optimizer with a learning rate of 0.01."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "pTV-k6t4juMW"
   },
   "outputs": [],
   "source": [
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "7Uso46P7juMa"
   },
   "outputs": [],
   "source": [
    "from torch_points3d.metrics.colored_tqdm import Coloredtqdm as Ctq\n",
    "\n",
    "def train_epoch(device):\n",
    "    model.to(device)\n",
    "    model.train()\n",
    "    tracker.reset(\"train\")\n",
    "    train_loader = dataset.train_dataloader\n",
    "    iter_data_time = time.time()\n",
    "    with Ctq(train_loader) as tq_train_loader:\n",
    "        for i, data in enumerate(tq_train_loader):\n",
    "            t_data = time.time() - iter_data_time\n",
    "            iter_start_time = time.time()\n",
    "            optimizer.zero_grad()\n",
    "            data.to(device)\n",
    "            model.forward(data)\n",
    "            model.backward()\n",
    "            optimizer.step()\n",
    "            if i % 10 == 0:\n",
    "                tracker.track(model)\n",
    "\n",
    "            tq_train_loader.set_postfix(\n",
    "                **tracker.get_metrics(),\n",
    "                data_loading=float(t_data),\n",
    "                iteration=float(time.time() - iter_start_time),\n",
    "            )\n",
    "            iter_data_time = time.time()\n",
    "\n",
    "def test_epoch(device):\n",
    "    model.to(device)\n",
    "    model.eval()\n",
    "    tracker.reset(\"test\")\n",
    "    test_loader = dataset.test_dataloaders[0]\n",
    "    iter_data_time = time.time()\n",
    "    with Ctq(test_loader) as tq_test_loader:\n",
    "        for i, data in enumerate(tq_test_loader):\n",
    "            t_data = time.time() - iter_data_time\n",
    "            iter_start_time = time.time()\n",
    "            data.to(device)\n",
    "            model.forward(data)           \n",
    "            tracker.track(model)\n",
    "\n",
    "            tq_test_loader.set_postfix(\n",
    "                **tracker.get_metrics(),\n",
    "                data_loading=float(t_data),\n",
    "                iteration=float(time.time() - iter_start_time),\n",
    "            )\n",
    "            iter_data_time = time.time()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 594,
     "referenced_widgets": [
      "9356ad5970d54c528b99a5123a60cea2",
      "d253410a9eb44a1fab5a67cdc5fdde42",
      "c8152fed712f4c378f0c82949bd99c65",
      "ba7c0485fe7e4189aa02c1605cf78f88",
      "f7f0d425bce54f0fa0a7a1029a8df3a0",
      "2151ee6bc3244827b5d58268d4afa180",
      "18d339a777454f3ea9d904565d1b039c",
      "7c4b51b608344736a48c79225acde1b9",
      "451ef9e739ab4102b7e81e25cf606cf3",
      "36d24f0eb1954d6e9046f1786bdb3d37",
      "3de667f1bc2844edb625df5b5e5a0a81",
      "b8b930418bcb43fd8e0827cf15bba828",
      "dbed69f8b4434f0f807d77e2ec475cb6",
      "eb6e8b18cb6d4d07bfd1efdd4497e20a",
      "678f0a76a0a24300ae00431bc7e7089b",
      "4b824a8f1c5f4b6fbfc3207a4d004d4a",
      "3ab9bdfebdb742e8a58072743bb73b20",
      "dc5797a405bf420abd94e722239859a9",
      "07dcbf9a1d97499c8edd32a96c3a443e",
      "d2478fba243d40ba8b6c324fd33a2ef2",
      "710df54acf3746e08f4558d153e45e37",
      "e333f564560d40e6843a1a41f064cf35",
      "90ec695d548642d180d8497a30a2b1f4",
      "796f9f7b5a934149b0122c69624411d0",
      "d3ea1518f7db4cdd8a679819168c2164",
      "d2bcb9b9fe67455abb7b65ae23f1128e",
      "4df14c2b484a4344b048ba37666c38a6",
      "e6318da159e74bc0a90eac731875fef0",
      "f435adc8a0904b12a5c59ecc6dcd391c",
      "a4cb579db2ae4416b3f041632ff6a1f6",
      "c75b805e551e48dbbbc39871d4b70a6a",
      "ab7a1682a0db4f0288c2466e548ff1c8",
      "b502cd2db737485abef2a26ec82bcc28",
      "95a73ae5d0684b60841335ac8787d975",
      "c53c0cec649846dba3d97c30a5b2f233",
      "c9182db9655649218188fa900b632d7f",
      "247f4c66af62407c9ac40c3b7f77246b",
      "ceca76b477d1489597b3a31cd2cc4546",
      "84cb21f4748d46128cc3d7ba28a0128a",
      "b3c3c260212c458db1b69f4374b9293d",
      "ded61346f4a345bf868c4845c2caf1b8",
      "d6a2215348474dc7bd5dfce5fa953523",
      "c7e44ae745c74c5fbf7639f09173cfb5",
      "e6a4e4397ec145b386d8b3475d047b66",
      "fd1c7f6b7efc46458609ecc74701d426",
      "1798e045199a484b97da96c3a253571a",
      "3f47a927657841709998ea217124d2bb",
      "74941e6fb73d427588a01f2206443dde",
      "e2c2e9e41232477ba3e05df3592efba2",
      "6b2e3f6b4d62473ca9626a0afbd44993",
      "02fb4650415b42fd82cba3a5d716ac98",
      "d781e717126944979d88c80bfd0836c0",
      "45f3610583a94dc3bd3765c42b81df98",
      "2eb3bd2a4cf24029820a68cce7632827",
      "7d37e913cd9b4c8f9634e387e0fdb49c",
      "8eaf1be839ed4625a4ac4c7cf5a05b03",
      "5ff4e07f033d48cc8aa731897356e1e1",
      "789f392fc46d4820a11588256a81c38c",
      "f938ab1a07974328807cfb85d8f6618d",
      "ed959a244ba34cf9a7202e8baa634282",
      "1b5c43f935e5413c8c8c75753d7f3fa6",
      "3de8e486f56c4699bf7c2c2cd9aaa3f8",
      "2dd7069d2b6b4b0aabb8efae69c10faa",
      "335ef78c7aa44d0bb8dd078e1fa9070d",
      "eb4fa24686694fd389fce44b10a05c62",
      "526c8e425f004736bb8bb8b5a8296995",
      "3cb5536a1ad944089e2bd7cd53f95051",
      "b14581fcc84d404cb765df552988b896",
      "ef606f5dcf91446d960eae089a46bbc2",
      "2bd6d4a1b0934311964dafa439c7a99d",
      "8c9dc0edb2e24876be211bedcf4980b6",
      "2d8cd5f359a14c1a9d989ab0001d4109",
      "c28dbbbad2914215bf51fdbf95a4921a",
      "6db8e9284672427a9344b66fcd73bfb9",
      "06addc65ecf844b480bfb7fb450b3754",
      "76b078d22284471693b74e064dd38d84",
      "2f89d85e5b1046848e39719cd207dc29",
      "f347e6f996ee451abc1caac0b89aa43a",
      "f726649a59614bc3b89ec8e8f0ba9694",
      "7562dad49922423789fcdc59d490870b"
     ]
    },
    "colab_type": "code",
    "id": "mfuxvTRbjuMe",
    "outputId": "5ddd472d-3364-41e3-f1be-445376869769"
   },
   "outputs": [],
   "source": [
    "EPOCHS = 50\n",
    "for i in range(EPOCHS):\n",
    "  print(\"=========== EPOCH %i ===========\" % i)\n",
    "  time.sleep(0.5)\n",
    "  train_epoch('cuda')\n",
    "  tracker.publish(i)\n",
    "  test_epoch('cuda')\n",
    "  tracker.publish(i)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "Bak51OOoBSi3"
   },
   "outputs": [],
   "source": [
    "%tensorboard --logdir tensorboard"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "N47pg8HjRRSy"
   },
   "source": [
    "![image.png]()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "b4bjdptkX79f"
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "include_colab_link": true,
   "machine_shape": "hm",
   "name": "ObjectClassificationRSConv.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
